package com.tulink.yichutu.biz;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.google.common.collect.Lists;
import com.mxixm.fastboot.weixin.module.extend.WxQrCode;
import com.mxixm.fastboot.weixin.module.message.WxUserMessage;
import com.mxixm.fastboot.weixin.module.user.WxUser;
import com.mxixm.fastboot.weixin.service.WxApiService;
import com.mxixm.fastboot.weixin.service.WxExtendService;
import com.tulink.yichutu.common.exception.BusinessException;
import com.tulink.yichutu.common.utils.DateUtils;
import com.tulink.yichutu.common.utils.RSAUtils;
import com.tulink.yichutu.dto.HeaderDataDto;
import com.tulink.yichutu.dto.OfflineVerifyCodeDto;
import com.tulink.yichutu.dto.WxUserDto;
import com.tulink.yichutu.entity.WxLoginLogEntity;
import com.tulink.yichutu.entity.WxQrcodeEntity;
import com.tulink.yichutu.entity.WxUserEntity;
import com.tulink.yichutu.service.RedisService;
import com.tulink.yichutu.service.WxLoginLogService;
import com.tulink.yichutu.service.WxQrcodeService;
import com.tulink.yichutu.service.WxUserService;
import com.tulink.yichutu.vo.resp.QrcodeRespVO;
import com.tulink.yichutu.vo.resp.WxLoginRespVO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import javax.servlet.http.HttpServletRequest;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 登录业务方法
 *
 * @author wanchaopeng
 */
@Slf4j
@Component
public class LoginBiz {

    public final static int Qrcode_expireSeconds = 300;
    public final static int Max_devices = 1;
    public final static int NewUser_EndTimeDays = 90;
    public final static int Ticks = 5;
    private final static String VerifyCode_PublicKey = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCaew0xypZ2s4IFa6kF49efaA+fvzuovONMUIYNKVcXm4/9ZpIQujd5jsAPMtN1zlHV+a6JEPbosJElyjt9QZBt/ruMn6FQVzBo4VSdre9iGA2QYx6uBHsatex+ub9srWY3EU1Mfez3PGa1CK7DfxvcdXIRV1V4fIkBV9AGjtVGVwIDAQAB";
    private final static String VerifyCode_PrivateKey = "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAJp7DTHKlnazggVrqQXj159oD5+/O6i840xQhg0pVxebj/1mkhC6N3mOwA8y03XOUdX5rokQ9uiwkSXKO31BkG3+u4yfoVBXMGjhVJ2t72IYDZBjHq4Eexq17H65v2ytZjcRTUx97Pc8ZrUIrsN/G9x1chFXVXh8iQFX0AaO1UZXAgMBAAECgYBmqdRw/LPWGyITvCLOOUc5Nwz7Gznov+9G7lTINmsc01N0ElCMMVVgwY++CgciSYV1cMOfb9jHd2Be/vFeIs56YvLBAS4SeCN2F5oFbDTsZ94T3Oa8xiEbgEeIPIkq3AHMG6n5a1OOypsR9pDgAF2ZB+DKttV0FRvpV8cbMMm7MQJBAMksxoxlfMJ/9gRTvse0JmsjUu7xSo8/0MEqhXt93ksVZggtUALjBKzvOx1osz7ViQOFs77XJ6Hi9VTCPbi12JMCQQDElJfOR/7aBhMVthx/NziLOIiN2Qtna2gKxQzu6+u3SuEbzdIPHg99YzTVf15J56OG1mEZ3KkHaecsBOpIEEmtAkB07PRdp6LyvUL1UuNg2SE3RimuvUmzpsijbR52HzszcrWB7SxC8UXHaah3OGJuE9h11qEZO55KZkpnQBp1oCPHAkAnYkXPtCjg8VqdfPDUo/GWIutoz9UzERe1IKlorKd6/2t+MIwnLzSXQGrzYyM210ux1f16OvU6YQNJlGqDqY+5AkEAtgoNz0FCu0Gq3oNos+JTYqUJ9oRdiciHNu5Q6ZWeTzTNnFg1e5jAdO4fsYs/XCHTsAMxVHjohwHi02Gvl+0HTw==";


    @Autowired
    private RedisService redisService;
    @Autowired
    private WxExtendService wxExtendService;
    @Autowired
    private WxUserService wxUserService;
    @Autowired
    private WxQrcodeService wxQrcodeService;
    @Autowired
    private WxLoginLogService wxLoginLogService;
    @Autowired
    private UserCacheBiz userCacheBiz;
    @Autowired
    private VipDateBiz vipDateBiz;
    @Autowired
    private WxApiService wxApiService;
    @Autowired
    private GoodsBiz goodsBiz;
    @Autowired
    private WxBiz wxBiz;

    public void unsubscribe(WxUser wxUser) {
        userCacheBiz.delOpenId(wxUser.getOpenId());
        wxUserService.update(Wrappers.<WxUserEntity>lambdaUpdate().set(WxUserEntity::getSubscribe, 0).eq(WxUserEntity::getOpenId, wxUser.getOpenId()));
    }

    /**
     * 生成带参二维码
     * @return
     */
    public QrcodeRespVO genQrcode(String scene, String deviceCode) {
        QrcodeRespVO respVO = new QrcodeRespVO();
        WxQrCode.Result result = wxExtendService.createQrCode(WxQrCode.builder().temporary(Qrcode_expireSeconds, scene).build());
        String key = getSceneKey(scene);
        BeanUtils.copyProperties(result, respVO);
        respVO.setId(scene);
        respVO.setDeviceCode(deviceCode);
        respVO.setTicks(Ticks);
        redisService.setAndExpire(key, JSON.toJSONString(respVO), Qrcode_expireSeconds);
        log.info("生成二维码:{}, {}", scene, deviceCode);
        return respVO;
    }

    /**
     * 处理扫描二维码事件
     * @param scene
     * @return
     */
    public String handleQrcodeScan(String scene, WxUser wxUser) {
        try {
            String key = getSceneKey(scene);
            String value = redisService.get(key);
            String verifyCodeKey = getOfflineLoginKey(scene);
            String verifyCode = redisService.get(verifyCodeKey);
            WxQrcodeEntity newQrcodeEntity = null;
            String deCode = null;
            String userId;
            try {
                if (value == null) {
                    return "二维码已过期，请刷新后重新扫码";
                }
                QrcodeRespVO qrcodeRespVO = JSON.parseObject(value, QrcodeRespVO.class);
                WxQrcodeEntity qrcodeEntity = wxQrcodeService.getOne(Wrappers.<WxQrcodeEntity>lambdaQuery().eq(WxQrcodeEntity::getScene, scene));
                if (qrcodeEntity != null && !qrcodeEntity.getOpenId().equals(wxUser.getOpenId())) {
                    return "二维码已被其他用户扫描，请刷新后重新扫码";
                }
                if (!StringUtils.isEmpty(verifyCode)) {
                    //离线扫码
                    OfflineVerifyCodeDto verifyCodeDto = JSON.parseObject(verifyCode, OfflineVerifyCodeDto.class);
                    try {
                        deCode = getDeCode(verifyCodeDto);
                        if (StringUtils.isEmpty(deCode)) {
                            return "离线登录失败，请加QQ群：1030021644、722620963反馈！\r\n" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                        }
                    } catch (BusinessException ex) {
                        return "离线登录失败，" + ex.getMessage() + "，了解更多，请加QQ群：1030021644、722620963反馈！\r\n" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
                    }
                }

                //保存用户
                WxUserEntity newEntity = new WxUserEntity();
                BeanUtils.copyProperties(wxUser, newEntity);
                newEntity.setNickname(wxUser.getNickName());
                newEntity.setTagIdList(wxUser.getTagIdList() != null ? wxUser.getTagIdList().stream().map(String::valueOf).collect(Collectors.joining()) : "");
                WxUserEntity userEntity = wxUserService.getOne(Wrappers.<WxUserEntity>lambdaQuery().eq(WxUserEntity::getOpenId, wxUser.getOpenId()));
                if (userEntity != null) {
                    newEntity.setId(userEntity.getId());
                    wxUserService.updateById(newEntity);
                } else {
                    wxUserService.save(newEntity);
                    //新用户，默认充值3个月（90天）
                    vipDateBiz.updateEndTime(newEntity.getId(), NewUser_EndTimeDays);
                }
                userId = newEntity.getId();
                userCacheBiz.addOpenId(wxUser.getOpenId(), userId);

                //保存二维码扫码记录
                newQrcodeEntity = new WxQrcodeEntity();
                newQrcodeEntity.setExpireSeconds(Qrcode_expireSeconds);
                newQrcodeEntity.setOpenId(wxUser.getOpenId());
                newQrcodeEntity.setScene(scene);
                newQrcodeEntity.setDeviceCode(qrcodeRespVO.getDeviceCode());
                newQrcodeEntity.setVerifyCode(deCode);
                if (qrcodeEntity != null) {
                    newQrcodeEntity.setId(qrcodeEntity.getId());
                    wxQrcodeService.updateById(newQrcodeEntity);
                } else {
                    wxQrcodeService.save(newQrcodeEntity);
                }

                WxUserDto userDto = new WxUserDto();
                BeanUtils.copyProperties(newEntity, userDto);
                userDto.setDeviceCode(qrcodeRespVO.getDeviceCode());
                redisService.setAndExpire(getLoginKey(scene), JSON.toJSONString(userDto), Qrcode_expireSeconds);
            } finally {
                redisService.del(key);
                if (verifyCode != null) {
                    redisService.del(verifyCodeKey);
                }
            }
            Date vipEndDate = vipDateBiz.getVipEndTime(userId);
            if(vipDateBiz.isVip(vipEndDate)) {
                //扫码成功，且是vip
                if (!StringUtils.isEmpty(verifyCode)) {
                    //离线扫码
                    wxQrcodeService.update(
                            Wrappers.<WxQrcodeEntity>lambdaUpdate()
                                    .set(WxQrcodeEntity::getVerifyCode, deCode)
                                    .eq(WxQrcodeEntity::getId, newQrcodeEntity.getId()));
                    if(vipDateBiz.isLifetime(vipEndDate)) {
                        return String.format(
                                "登录成功，验证码：%s\r\n\r\n您的会员有效期：%s",
                                deCode,
                                "永久");
                    } else {
                        return String.format(
                                "登录成功，验证码：%s\r\n\r\n您的会员有效期：%s",
                                deCode,
                                VipDateBiz.getEndTimeFormat().format(vipEndDate));
                    }
                } else {
                    //在线扫码
                    if(vipDateBiz.isLifetime(vipEndDate)) {
                        return String.format(
                                "登录成功！\r\n\r\n您的会员有效期：%s",
                                "永久");
                    } else {
                        return String.format(
                                "登录成功！\r\n\r\n您的会员有效期：%s",
                                VipDateBiz.getEndTimeFormat().format(vipEndDate));
                    }
                }
            } else {
                //扫码成功，但不是vip
                return "非常抱歉，您还不是会员，或会员已到期，请点击菜单 \"会员\" -> \"充值\" 进行充值\r\n\r\n" +
                        "充值后请刷新易出图登录二维码，重新扫码登录\r\n\r\n" +
                        "企业版请咨询客服QQ号：2182347271";
            }
        } catch (Exception ex) {
            ex.printStackTrace();
            log.error("扫码失败", ex);
            return "扫码失败，请加QQ群：1030021644、722620963反馈！";
        }
    }

    /**
     * 登录
     * @return
     */
    public WxLoginRespVO tryLogin(String scene, HttpServletRequest request) {
        WxLoginRespVO respVO = new WxLoginRespVO();
        String value = redisService.get(getSceneKey(scene));
        if(value != null) {
            //等待扫码
            respVO.setStatus(0);
            return respVO;
        }

        value = redisService.get(getLoginKey(scene));
        if(value == null) {
            //登录失败
            respVO.setStatus(2);
            log.info("扫码登录失败，二维码过期:{}", scene);
            return respVO;
        }
        WxUserDto userDto = JSON.parseObject(value, WxUserDto.class);
        //会员校验逻辑
        if(!validVip(userDto.getId())) {
            //到期
            respVO.setStatus(2);
            log.info("扫码登录失败，不是会员或会员到期:{}", scene);
            return respVO;
        }
        respVO.setStatus(1);
        respVO.setUserId(userDto.getId());
        respVO.setNickname(userDto.getNickname());
        respVO.setHeadImgUrl(userDto.getHeadImgUrl());
        respVO.setDeviceCode(userDto.getDeviceCode());
        saveLoginLog(respVO, getHeaderData(request), scene);
        log.info("扫码登录成功:{}", scene);
        return respVO;
    }

    /**
     * 登录
     * @return
     */
    public WxLoginRespVO login(String userId, String deviceCode, HttpServletRequest request) {
        WxLoginRespVO respVO = new WxLoginRespVO();
        WxUserEntity userEntity = wxUserService.getById(userId);
        if(userEntity == null) {
            respVO.setStatus(2);
            log.info("自动登录失败，用户不存在:{}，{}", userId, deviceCode);
            return respVO;
        }
        if(userEntity.getSubscribe() == 0) {
            //取消订阅
            respVO.setStatus(2);
            log.info("自动登录失败，用户已取消订阅:{}，{}", userId, deviceCode);
            return respVO;
        }
        List<WxQrcodeEntity> qrcodeEntities = wxQrcodeService.list(
                Wrappers.<WxQrcodeEntity>lambdaQuery()
                        .eq(WxQrcodeEntity::getOpenId, userEntity.getOpenId())
                        .orderByAsc(WxQrcodeEntity::getUpdateTime)
        );
        if(qrcodeEntities.size() < 1) {
            //没有扫码记录，登录失败
            respVO.setStatus(2);
            log.info("自动登录失败，没有扫码记录:{}，{}", userId, deviceCode);
            return respVO;
        }
        List<WxQrcodeEntity> allowDevices = qrcodeEntities.subList(Math.max(0, qrcodeEntities.size() - Max_devices), qrcodeEntities.size());
        if(!allowDevices.stream().anyMatch(p -> p.getDeviceCode().equals(deviceCode))) {
            //没有扫码记录，登录失败
            respVO.setStatus(2);
            log.info("自动登录失败，在其他设备有登录记录:{}，{}", userId, deviceCode);
            return respVO;
        }
        //会员校验逻辑
        if(!validVip(userEntity.getId())) {
            //到期
            respVO.setStatus(2);
            log.info("自动登录失败，不是会员或会员到期:{}，{}", userId, deviceCode);
            return respVO;
        }

        respVO.setStatus(1);
        respVO.setUserId(userEntity.getId());
        respVO.setHeadImgUrl(userEntity.getHeadImgUrl());
        respVO.setNickname(userEntity.getNickname());
        respVO.setDeviceCode(deviceCode);
        saveLoginLog(respVO, getHeaderData(request), null);
        log.info("自动登录成功:{}，{}, {}, {}, {}", userId, deviceCode, userEntity.getNickname(), userEntity.getCountry(), userEntity.getCity());
        return respVO;
    }

    /**
     * 离线登录
     * @param qrcodeRespVO
     * @param verifyCodeDto
     * @return
     */
    public void offlineLogin(QrcodeRespVO qrcodeRespVO, OfflineVerifyCodeDto verifyCodeDto) {
        redisService.setAndExpire(getOfflineLoginKey(qrcodeRespVO.getId()), JSON.toJSONString(verifyCodeDto), Qrcode_expireSeconds);
        log.info("离线登录:{}", qrcodeRespVO.getDeviceCode());
    }

    /**
     * 清理扫码记录
     * @return
     */
    public Integer cleanQrcodeScanRecord() {
        int removeCount = 0;
        List<List<String>> openIdsList = Lists.partition(userCacheBiz.getAllOpenIds(), 500);
        for(List<String> openIds : openIdsList) {
            if(openIds.size() > 0) {
                Map<String, List<WxQrcodeEntity>> qrcodeEntityMap = wxQrcodeService.list(
                        Wrappers.<WxQrcodeEntity>lambdaQuery()
                                .select(WxQrcodeEntity::getId, WxQrcodeEntity::getOpenId)
                                .in(WxQrcodeEntity::getOpenId, openIds)
                                .orderByAsc(WxQrcodeEntity::getUpdateTime)
                ).stream().collect(Collectors.groupingBy(WxQrcodeEntity::getOpenId));
                List<String> qrcodeIds = new ArrayList<>();
                for(Map.Entry<String, List<WxQrcodeEntity>> entry : qrcodeEntityMap.entrySet()) {
                    if(entry.getValue().size() > Max_devices) {
                        qrcodeIds.addAll(entry.getValue().subList(0, entry.getValue().size() - Max_devices).stream().map(WxQrcodeEntity::getId).collect(Collectors.toList()));
                    }
                }
                if(qrcodeIds.size() > 0) {
                    wxQrcodeService.removeByIds(qrcodeIds);
                    removeCount += qrcodeIds.size();
                }
            }
        }
        return removeCount;
    }

    private Boolean validVip(String userId) {
        return vipDateBiz.isVip(vipDateBiz.getVipEndTime(userId));
    }

    private HeaderDataDto getHeaderData(HttpServletRequest request) {
        HeaderDataDto headerDataDto = new HeaderDataDto();
        try {
            headerDataDto.setCadBit(request.getHeader("cad_bit"));
            headerDataDto.setCadVersion(request.getHeader("cad_version"));
            headerDataDto.setComputerName(request.getHeader("computer_name"));
            headerDataDto.setMac(request.getHeader("mac"));
            headerDataDto.setSystemVersion(request.getHeader("system_version"));
            headerDataDto.setYichutuVersion(request.getHeader("yichutu_version"));
        } catch (Exception ex) {
            log.error("获取埋点数据失败", ex);
        }
        return headerDataDto;
    }

    /**
     * 保存登录日志
     */
    private void saveLoginLog(WxLoginRespVO loginRespVO, HeaderDataDto headerDataDto, String scene) {
        try {
            WxLoginLogEntity loginLogEntity = new WxLoginLogEntity();
            loginLogEntity.setDeviceCode(loginRespVO.getDeviceCode());
            loginLogEntity.setScene(scene);
            loginLogEntity.setUserId(loginRespVO.getUserId());
            loginLogEntity.setCadBit(headerDataDto.getCadBit());
            loginLogEntity.setCadVersion(headerDataDto.getCadVersion());
            loginLogEntity.setComputerName(headerDataDto.getComputerName());
            loginLogEntity.setMac(headerDataDto.getMac());
            loginLogEntity.setSystemVersion(headerDataDto.getSystemVersion());
            loginLogEntity.setYichutuVersion(headerDataDto.getYichutuVersion());
            wxLoginLogService.save(loginLogEntity);
        } catch (Exception ex) {
            log.error("保存登录日志失败", ex);
        }
    }

    /**
     * 获取解码后的验证码
     * @param verifyCodeDto
     * @return
     */
    private String getDeCode(OfflineVerifyCodeDto verifyCodeDto) {
        String verifyCode = RSAUtils.decryptDataOnJava(verifyCodeDto.getVerifyCode(), VerifyCode_PrivateKey);
        if(StringUtils.isEmpty(verifyCode)) {
            log.warn("解密失败，{}", verifyCodeDto.toString());
            return verifyCode;
        }

        Integer verifyCodeLenght = verifyCodeDto.getVerifyCodeLength() == null ? 6 : verifyCodeDto.getVerifyCodeLength();
        verifyCodeLenght = Math.max(1, verifyCodeLenght);
        if(verifyCode.length() < verifyCodeLenght) {
            log.warn("验证码长度错误，{}", verifyCodeDto.toString());
            return "";
        }
        int index = verifyCode.indexOf("-", verifyCodeLenght);
        if(index < 0) {
            log.warn("验证码格式错误，{}", verifyCodeDto.toString());
            return "";
        }
        long timeStamp = Long.parseLong(verifyCode.substring(verifyCodeLenght, index));
        long nowTimeStamp = System.currentTimeMillis();
        long expireSeconds = Math.abs(nowTimeStamp - timeStamp) / 1000;
        if(expireSeconds > Qrcode_expireSeconds * 100) {
            log.warn("电脑时间与北京时间不匹配，{}", verifyCodeDto.toString());
            throw new BusinessException("电脑时间与北京时间不匹配，请修正您的电脑时间");
        }
        if(verifyCode.substring(index + 1).equalsIgnoreCase(verifyCodeDto.getDeviceCode())) {
            return verifyCode.substring(0, verifyCodeLenght);
        } else {
            log.warn("设备编码验证不通过，{}", verifyCodeDto.toString());
            return "";
        }
    }

    /**
     * 获取二维码参数Redis Key
     * @param scene
     * @return
     */
    private String getSceneKey(String scene) {
        return String.format("scene:%s", scene);
    }

    /**
     * 获取二维码参数Redis Key
     * @param scene
     * @return
     */
    private String getLoginKey(String scene) {
        return String.format("login:%s", scene);
    }

    /**
     * 获取二维码参数Redis Key
     * @param scene
     * @return
     */
    private String getOfflineLoginKey(String scene) {
        return String.format("offlineLogin:%s", scene);
    }
}
